﻿using System;
using System.Collections.Generic;
using OverStore.Runtime.PersistenceServiceProvider;
using OverStore.Runtime.ObjectRepository;
using OverStore.Runtime.Storage;
using OverStore.Runtime.Logger;
using OverStore.Runtime.ObjectRepository.Mapping;
using OverStore.Runtime.ObjectRepository.IdentifierProvider;

namespace OverStore.Runtime.ObjectRepository.Reader
{

    /// <summary>
    /// Reads sequence of objects of <typeparamref name="TItem"/> type using values from object of <typeparamref name="TParameter"/> type.
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance.</typeparam>
    /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
    /// <typeparam name="TParameter">The type of the parameter.</typeparam>
    /// <remarks>
    /// For each new row of data read from storage read variant at first extracts
    /// identifier using specified <see cref="IIdentifierProvider{TItemIdentifier}"/>.
    /// Then using instance of <see cref="IEntityRegistry{TItem, TIdentifier}"/> it obtain instance with
    /// extracted identifier. If <paramref name="useResultSetForObjectConstruction"/> read variant specifies
    /// additional argument to IRegistry.GetInstance method.
    /// This argument allows registry to use specified data for load instance requested by read variant.
    /// It significantly improves performance of reading.
    /// </remarks>
    public sealed class ParameterizedReader<TInstance, TIdentifier, TParameter> : IReader<TInstance, TIdentifier>
        where TInstance : class
        where TParameter : class
    {
        private readonly IIdentifierProvider<TIdentifier> _IdentifierProvider;
        private readonly IMapper<TParameter> _ParameterMapper;
        private readonly ICommand _ReadCommand;
        private readonly IDataStorage _Storage;
        private readonly ILogger _Logger;
        private readonly bool _UseResultSetForObjectConstruction;
        private readonly IPersistenceServiceProvider _Provider;

        /// <summary>
        /// Initializes a new instance of the <see cref="ParameterizedReader{TInstance, TIdentifier, TParameter}"/> class.
        /// </summary>
        /// <param name="identifierProvider">The identifier provider used for obtain instance identifier from data.</param>
        /// <param name="provider">Object provides access to different services that allows to perform different task regarding persisting instance of specified type.</param>
        /// <param name="parameterMapper">The <see cref="IMapper{TParameter}"/> uses for get command parameters from <typeparamref name="TParameter"/>
        /// instance.</param>
        /// <param name="readCommand">The <see cref="ICommand"/> used for access data storage.</param>
        /// <param name="storage">The storage used for read data.</param>
        /// <param name="logger">The logger.</param>
        /// <param name="useResultSetForObjectConstruction">If set to <c>true</c> ExecuteRead method uses
        /// <see cref="IRecord"/> read from storage used for constructing instances of <typeparamref name="TItem"/>;
        /// otherwise method uses only identifier for access instances via <see cref="IEntityRegistry{TInstance, TIdentifier}"/>.</param>
        /// <exception cref="ArgumentNullException"><paramref name="identifierProvider"/> is null reference.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="provider"/> is null reference.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="parameterMapper"/> is null reference.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="readCommand"/> is null reference.</exception>
        public ParameterizedReader(
            IIdentifierProvider<TIdentifier> identifierProvider,
            IPersistenceServiceProvider provider,
            IMapper<TParameter> parameterMapper,
            ICommand readCommand,
            IDataStorage storage,
            ILogger logger,
            bool useResultSetForObjectConstruction)
        {
            if (provider == null)
                throw new ArgumentNullException("provider");
            _Provider = provider;
            if (parameterMapper == null)
                throw new ArgumentNullException("parameterMapper");
            if (readCommand == null)
                throw new ArgumentNullException("readCommand");
            _ParameterMapper = parameterMapper;
            _ReadCommand = readCommand;
            _UseResultSetForObjectConstruction = useResultSetForObjectConstruction;
            if (identifierProvider == null)
                throw new ArgumentNullException("identifierProvider");
            _IdentifierProvider = identifierProvider;
            if (storage == null)
                throw new ArgumentNullException("storage");
            _Storage = storage;
            _Logger = logger;
        }

        /// <summary>
        /// Execute object reader with specific parameters.
        /// </summary>
        /// <param name="parameters">The parameters of data storage command.</param>
        /// <returns>
        /// The sequence of object read from data storage.
        /// </returns>
        public IEnumerable<TInstance> ExecuteRead(TParameter parameters)
        {
            IRecord parametersData;
            parametersData = _ParameterMapper.GetObjectData(parameters, _Provider);

            return new ResultSetEnumerable<TInstance, TIdentifier>(
                _ReadCommand,
                _Storage,
                parametersData,
                _IdentifierProvider,
                _Logger,
                _UseResultSetForObjectConstruction,
                _Provider);
        }

        /// <summary>
        /// Gets the type of the parameter for reader or null if reader does not expect any parameter.
        /// </summary>
        /// <value>The type of the parameter.</value>
        Type IReader<TInstance, TIdentifier>.ParameterType { get { return typeof(TParameter); } }

        /// <summary>
        /// Executes the reader with specified parameters. <paramref name="parameter"/> may be null
        /// if implementation supports it.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        /// <returns></returns>
        IEnumerable<TInstance> IReader<TInstance, TIdentifier>.ExecuteReader(object parameter)
        {
            return ExecuteRead((TParameter)parameter);
        }
    }
}